package com.leofee.servlet;

import com.leofee.annotation.MyController;
import com.leofee.annotation.MyRequestMapping;
import com.leofee.utils.CollectionUtil;
import com.leofee.utils.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

public class MyDispatcherServlet extends MyHttpServletBean {

    private static final Logger log = Logger.getLogger(MyDispatcherServlet.class + "");

    /**
     * 模拟的ioc容器
     */
    private static Map<String, Object> ioc = new HashMap<>();

    private static final Map<String, Method> handlerMappings = new HashMap<>();

    private static final Map<String, Object> controllerMappings = new HashMap<>();

    static {

    }

    @Override
    public void onRefresh() {
        log.info(getClass() + " begin onRefresh...");

        initStrategies();

        log.info(getClass() + " end onRefresh...");
    }

    /**
     * 初始化
     */
    private void initStrategies() {
        // 扫描class
        List<String> classNames = scanPackage(getProperties().getProperty("scan_package"));

        // 实例化
        instantiate(classNames);

        // 初始化handler mapping
        initHandlerMapping();

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) {
        try {
            doDispatch(req, res);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) {
        try {
            doDispatch(req, res);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse res) throws IOException {

        String uri = req.getRequestURI();

        Object controller = controllerMappings.get(uri);

        Method method = handlerMappings.get(uri);


        if (controller != null && method != null) {
            Object[] methodParams = new Object[method.getParameterTypes().length];

            req.getParameterMap();

            try {
                Object methodRes = method.invoke(controller, methodParams);
                log.info(method.getName() + " excute success, result is [" + methodRes + "]");
                res.getWriter().write(methodRes.toString());
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
                res.sendError(500);

            }
        } else {
            res.sendError(404);
        }

        log.info("accept request " + uri);
    }

    /**
     * 扫描配置文件中所有文件配置的扫描路径
     *
     * @param packagePath 配置的包路径
     * @return 配置的包路径的文件, 如果没有任何文件则返回 null
     */
    private List<String> scanPackage(String packagePath) {

        List<String> classNames = null;

        if (!StringUtils.isEmpty(packagePath)) {
            String tempPackagePath = packagePath.replaceAll("\\.", "/");

            URL url = this.getClass().getClassLoader().getResource(tempPackagePath);
            if (url == null) {
                return null;
            }

            String pathFile = url.getFile();
            File file = new File(pathFile);
            File[] files = file.listFiles();

            if (!CollectionUtil.isEmpty(files)) {
                classNames = new ArrayList<>();
                for (File item : files) {
                    if (item.isDirectory()) {

                        // 递归扫描
                        List<String> classNames2 = scanPackage(packagePath
                                .concat(".")
                                .concat(item.getName()));
                        if (!CollectionUtil.isEmpty(classNames2)) {
                            classNames.addAll(classNames2);
                        }
                    } else {
                        classNames.add(packagePath.concat(".")
                                .concat(item.getName()
                                .replaceAll("\\.class", "")));
                    }
                }
            }
        }
        return classNames;
    }

    /**
     * 实例化扫描到的class
     *
     * @param classNames 类名
     */
    private void instantiate(List<String> classNames) {
        if (CollectionUtil.isEmpty(classNames)) {
            return;
        }

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);

                System.out.println(clazz.isAnnotationPresent(MyRequestMapping.class));

                if (clazz.isAnnotationPresent(MyController.class)) {
                    Object instance = clazz.newInstance();
                    ioc.put(className, instance);
                }

            } catch (InstantiationException
                    | IllegalAccessException
                    | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 初始化request handler
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        Set<String> keys = ioc.keySet();
        for (String key : keys) {
            Object controller = ioc.get(key);

            String totalUrl = "";
            Class<?> clazz = controller.getClass();
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                totalUrl = annotation.value();
            }

            Method[] methods = clazz.getDeclaredMethods();
            if (CollectionUtil.isEmpty(methods)) {
                continue;
            }
            for (Method method : methods) {
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String methodUrl = annotation.value();
                    if (!StringUtils.isEmpty(methodUrl)) {
                        totalUrl = totalUrl.concat(methodUrl);
                        log.info("mappings :url " + totalUrl + "-- " + method.getName());
                        handlerMappings.put(totalUrl, method);
                        controllerMappings.put(totalUrl, controller);
                    }
                }
            }
        }
    }
}
